package slap.concurrent;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import slap.Slap;
import slap.annotation.AfterFrameworkInit;
import slap.annotation.Scan;
import slap.exception.SlapException;
import slap.exception.UnexpectedException;
import slap.paradigm.Procedure;
import slap.paradigm.Procedure.Procedure1;

@Scan
public class Invoker {
	protected static final Logger logger = LoggerFactory.getLogger(Invoker.class);
	public static ScheduledThreadPoolExecutor executor = null;
	
	@AfterFrameworkInit
	public static void init() {
		int core = 6;
		executor = new ScheduledThreadPoolExecutor(core, new PThreadFactory(
				"slap"), new ThreadPoolExecutor.AbortPolicy());
		//TODO setKeepAliveTime
		executor.setKeepAliveTime(3600, TimeUnit.SECONDS);
	}

	public static Future<?> invoke(final Invocation invocation) {
		return executor.submit(invocation);
	}

	public static Future<?> invoke(final Invocation invocation, long millis) {
		return executor.schedule(invocation, millis, TimeUnit.MILLISECONDS);
	}

	public static abstract class Invocation implements Runnable {

		/**
		 * Override this method
		 * 
		 * @throws java.lang.Exception
		 */
		public abstract void execute() throws Exception;

		/**
		 * Init the call (especially usefull in DEV mode to detect changes)
		 */
		public boolean init() {
			// Thread.currentThread().setContextClassLoader(Play.classloader);
			return true;
		}

		/**
		 * Things to do before an Invocation
		 */
		public void before() {
		}

		/**
		 * Things to do after an Invocation. (if the Invocation code has not
		 * thrown any exception)
		 */
		public void after() {
		}

		/**
		 * Things to do when the whole invocation has succeeded (before +
		 * execute + after)
		 */
		public void onSuccess() throws Exception {
		}

		/**
		 * Things to do if the Invocation code thrown an exception
		 */
		public void onException(Throwable e) {
			if (e instanceof SlapException) {
				throw (SlapException) e;
			}
			throw new UnexpectedException(e);
		}

		/**
		 * The request is suspended
		 * 
		 * @param suspendRequest
		 */
		public void suspend(Suspend suspendRequest) {
			if (suspendRequest.task != null) {
				WaitForTasksCompletion.waitFor(suspendRequest.task, this);
			} else {
				Invoker.invoke(this, suspendRequest.timeout);
			}
		}

		/**
		 * Things to do in all cases after the invocation.
		 */
		public void _finally() {
		}

		/**
		 * It's time to execute.
		 */
		public void run() {
			try {
				if (init()) {
					before();
					execute();
					after();
					onSuccess();
				}
			} catch (Suspend e) {
				suspend(e);
				after();
			} catch (Throwable e) {
				onException(e);
			} finally {
				_finally();
			}
		}
	}


	static class WaitForTasksCompletion extends Thread {

		static WaitForTasksCompletion instance;
		Map<Future<?>, Invocation> queue;

		public WaitForTasksCompletion() {
			queue = new ConcurrentHashMap<Future<?>, Invocation>();
			setName("WaitForTasksCompletion");
			setDaemon(true);
		}

		public static <V> void waitFor(Future<V> task,
				final Invocation invocation) {
			if (task instanceof Promise) {
				Promise<V> smartFuture = (Promise<V>) task;
				smartFuture.onRedeem(new Procedure1<Promise<V>>() {
					@Override
					public void invoke(Promise<V> result) {
						executor.submit(invocation);
					}
				});
			} else {
				synchronized (WaitForTasksCompletion.class) {
					if (instance == null) {
						instance = new WaitForTasksCompletion();
						logger.warn("Start WaitForTasksCompletion");
						instance.start();
					}
					instance.queue.put(task, invocation);
				}
			}
		}

		@Override
		public void run() {
			while (true) {
				try {
					if (!queue.isEmpty()) {
						for (Future<?> task : new HashSet<Future<?>>(
								queue.keySet())) {
							if (task.isDone()) {
								executor.submit(queue.get(task));
								queue.remove(task);
							}
						}
					}
					Thread.sleep(50);
				} catch (InterruptedException ex) {
					logger.warn( "While waiting for task completions", ex);
				}
			}
		}
	}

}
